"""PyCute shell for QT3, slightly modified.

Original at:
http://gerard.vermeulen.free.fr/examples/qwt/PyCute3.py.html

Eventually PyCrust will be ported to PyQt and all will be good.

Original top-of-file commentary follows:

In the future PyCute will get more features of the Idle's Python shell:
- fontification (syntax coloring)
- balloon help with documentation strings
- copy & paste into or out of the shell


Did you find a bug in PyCute? Check Idle's behavior before reporting.

There will be always some differences between a GUI shell and the
Python interpreter running in a terminal (Unix) or DOS box (Windows),
e.g:

os.system('dir') or os.system('ls') 

In a terminal or DOS box, the user sees the directory listing followed
by the return code (0). Why? In this case, stdout of the 'dir' or 'ls'
command coincides with stdout of the interpreter.

This is not the case with a GUI shell like PyCute or Idle. If the
shell has been started from a terminal or DOS box, the directory
listing will appear in the terminal or DOS box and the return code
will appear in the GUI shell.  If the GUI shell has been started by
other means, the return code of the command will appear in the shell
but the other behavior of is undefined (under Unix you will see
nothing, and under Windows you will see a DOS box flashing up).
"""

import os, sys
from interpreter import Interpreter
#from code import InteractiveInterpreter as Interpreter
#import qt
from PyQt4 import QtGui, QtCore

from pseudo import *

class PyCute(QtGui.QTextEdit):
    """PyCute is a Python shell for PyQt.

    Creating, displaying and controlling PyQt widgets from the Python
    command line interpreter is very hard, if not, impossible.  PyCute
    solves this problem by interfacing the Python interpreter to a
    PyQt widget.

    My use is interpreter driven plotting to QwtPlot instances. Why?
    
    Other popular scientific software packages like SciPy, SciLab,
    Octave, Maple, Mathematica, GnuPlot, ..., also have interpreter
    driven plotting.  It is well adapted to quick & dirty exploration.

    Of course, PyQt's debugger -- eric -- gives you similar
    facilities, but PyCute is smaller and easier to integrate in
    applications.  Eric requires Qt-3.x

    PyCute is based on ideas and code from:
    - Python*/Tools/idle/PyShell.py (Python Software Foundation
      License)
    - PyQt*/eric/Shell.py (Gnu Public License)
    """
    
    def __init__(self, locals=None, log='', parent=None):
        """Constructor.

        The optional 'locals' argument specifies the dictionary in
        which code will be executed; it defaults to a newly created
        dictionary with key '__name__' set to '__console__' and key
        '__doc__' set to None.

        The optional 'log' argument specifies the file in which the
        interpreter session is to be logged.
        
        The optional 'parent' argument specifies the parent widget.
        If no parent widget has been specified, it is possible to
        exit the interpreter by Ctrl-D.
        """

        QtGui.QTextEdit.__init__(self, parent)

        # Create a replacement for stdin.
        #self.reader = PseudoFileIn(self.readline, self.readlines)
        #self.reader.input = ''
        #self.reader.isreading = False

        self.interpreter = Interpreter(locals=locals,
                                       #stdin=self.reader,
                                       stdout=PseudoFileOut(self.writeOut),
                                       stderr=PseudoFileErr(self.writeErr))

        # session log
        self.log = log or ''

        # to exit the main interpreter by a Ctrl-D if PyCute has no parent
        if parent is None:
            self.eofKey = QtCore.Qt.Key_D
        else:
            self.eofKey = None

        # capture all interactive input/output 
        #sys.stdout = self
        #sys.stderr = self
        #sys.stdin = self

        # last line + last incomplete lines
        self.line = QtCore.QString()
        self.lines = []
        # the cursor position in the last line
        self.point = 0
        # flag: the interpreter needs more input to run the last lines. 
        self.more = 0
        # flag: readline() is being used for e.g. raw_input() and input()
        self.reading = 0
        # history
        self.history = []
        self.pointer = 0
        self.lastPos = 0        

        # user interface setup
        #self.setTextFormat(QtGui.QTextEdit.PlainText)
        #self.setWrapPolicy(QtGui.QTextEdit.Anywhere)
        #self.setCaption('PyCute -- a Python Shell for PyQt -- '
        #                'http://gerard.vermeulen.free.fr')
        # font
        if os.name == 'posix':
            font = QtGui.QFont("Bitstream Vera Sans Mono", 10)
        elif os.name == 'nt' or os.name == 'dos':
            font = QtGui.QFont("Courier New", 8)
        else:
            raise SystemExit, "FIXME for 'os2', 'mac', 'ce' or 'riscos'"
        font.setFixedPitch(1)
        self.setFont(font)

        # geometry
        height = 40 * QtGui.QFontMetrics(font).lineSpacing()
        request = QtCore.QSize(600, height)
        if parent is not None:
            request = request.boundedTo(parent.size())
        self.resize(request)

        # interpreter prompt.
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "

        # interpreter banner
        self.write('The PyCute shell running Python %s on %s.\n' %
                   (sys.version, sys.platform))
        self.write('Type "copyright", "credits" or "license"'
                   ' for more information on Python.\n')
        self.write(sys.ps1)
        self.lineStartPos = self.textCursor().position()

        # Find out for which keycodes the interpreter will autocomplete.
        self.autoCompleteKeys = self.interpreter.getAutoCompleteKeys()

    def flush(self):
        """
        Simulate stdin, stdout, and stderr.
        """
        pass

    def isatty(self):
        """
        Simulate stdin, stdout, and stderr.
        """
        return 1

    def writeOut(self, text):
        """Replacement for stdout."""
        self.write2(text)

    def writeErr(self, text):
        """Replacement for stderr."""
        self.write2(text)

    def write2(self, text):
        """Display text in the shell.

        Replace line endings with OS-specific endings."""
        text = self.fixLineEndings(text)
        self.write(text)

    def fixLineEndings(self, text):
        """Return text with line endings replaced by OS-specific endings."""
        lines = text.split('\r\n')
        for l in range(len(lines)):
            chunks = lines[l].split('\r')
            for c in range(len(chunks)):
                chunks[c] = os.linesep.join(chunks[c].split('\n'))
            lines[l] = os.linesep.join(chunks)
        text = os.linesep.join(lines)
        return text

    def readline(self):
        """
        Simulate stdin, stdout, and stderr.
        """
        self.reading = 1
        self.__clearLine()
        self.moveCursor(QtGui.QTextEdit.MoveEnd, 0)
        while self.reading:
            QtGui.qApp.processOneEvent()
        if self.line.length() == 0:
            return '\n'
        else:
            return str(self.line) 
    
    def write(self, text):
        """
        Simulate stdin, stdout, and stderr.
        """
        self.textCursor().insertText(text)

    def writelines(self, text):
        """
        Simulate stdin, stdout, and stderr.
        """
        map(self.write, text)
        print "DO WE EVER GET HERE? IF YES, OPTIMIZATION POSSIBLE"

    def fakeUser(self, lines):
        """
        Simulate a user: lines is a sequence of strings, (Python statements).
        """
        for line in lines:
            self.line = QtCore.QString(line.rstrip())
            self.write(self.line)
            self.write('\n')
            self.__run()
            
    def __run(self):
        """
        Append the last line to the history list, let the interpreter execute
        the last line(s), and clean up accounting for the interpreter results:
        (1) the interpreter succeeds
        (2) the interpreter fails, finds no errors and wants more line(s)
        (3) the interpreter fails, finds errors and writes them to sys.stderr
        """
        self.pointer = 0
        self.history.append(QtCore.QString(self.line))
        self.lines.append(str(self.line))
        source = '\n'.join(self.lines)
        self.more = self.interpreter.runsource(source)
        if self.more:
            self.write(sys.ps2)
        else:
            self.write(sys.ps1)
            self.lines = []
            self.lineStartPos = self.textCursor().position()
        self.__clearLine()
        self.ensureCursorVisible()
        
    def __clearLine(self):
        """
        Clear input line buffer
        """
        self.line.truncate(0)
        self.point = 0
        
    def __insertText(self, text):
        """
        Insert text at the current cursor position.
        """
        self.textCursor().insertText(text)
        self.line.insert(self.point, text)
        self.point += text.length()
        #self.setCursorPosition(y, x + text.length())
        self.ensureCursorVisible()

    def keyPressEvent(self, e):
        """
        Handle user input a key at a time.
        """
        #QtGui.QTextEdit.keyPressEvent(self, e)

        text = e.text()
        key = e.key()

        if text.length()  > 0:
            ascii = ord(str(text)[0])
            if ascii >= 32 and ascii < 127:
                self.__insertText(text)
                return

        if e.modifiers() & QtCore.Qt.ControlModifier and key == self.eofKey:
            try:
                file = open(self.log, "w")
                file.write(str(self.text()))
                file.close()
            except:
                pass
            sys.exit()
            return
        
        if e.modifiers() & QtCore.Qt.ControlModifier or e.modifiers() & QtCore.Qt.ShiftModifier:
            e.ignore()
            return

        #elif key in self.autoCompleteKeys:
        #    # Usually the dot (period) key activates auto completion.
        #    # Get the command between the prompt and the cursor.  Add
        #    # the autocomplete character to the end of the command.
        #    if self.AutoCompActive():
        #        self.AutoCompCancel()
        #    command = self.GetTextRange(stoppos, currpos) + chr(key)
        #    self.write(chr(key))
        #    if self.autoComplete:
        #        self.autoCompleteShow(command)
        if key == QtCore.Qt.Key_Backspace:
            if self.point:
                self.textCursor().deletePreviousChar()
                self.point -= 1
                self.line.remove(self.point, 1)
        elif key == QtCore.Qt.Key_Delete:
            self.doKeyboardAction(QtGui.QTextEdit.ActionDelete)
            self.line.remove(self.point, 1)
        elif key == QtCore.Qt.Key_Return or key == QtCore.Qt.Key_Enter:
            self.write('\n')
            if self.reading:
                self.reading = 0
            else:
                self.__run()
        elif key ==QtCore.Qt.Key_Tab:
            self.__insertText(text)
        elif key == QtCore.Qt.Key_Left:
            if self.point:
                self.moveCursor(QtGui.QTextEdit.MoveBackward, 0)
                self.point -= 1
        elif key == QtCore.Qt.Key_Right:
            if self.point < self.line.length():
                self.moveCursor(QtGui.QTextEdit.MoveForward, 0)
                self.point += 1
        elif key == QtCore.Qt.Key_Home:
            #self.setCursorPosition(self.yLast, self.xLast)
            cursor = self.textCursor()
            cursor.setPosition(self.lastPos)
            self.setTextCursor(cursor)
            self.point = 0
        elif key == QtCore.Qt.Key_End:
            self.moveCursor(QtGui.QTextEdit.MoveLineEnd, 0)
            self.point = self.line.length()
        elif key == QtCore.Qt.Key_Up:
            if len(self.history):
                if self.pointer == 0:
                    self.pointer = len(self.history)
                self.pointer -= 1
                self.__recall()
        elif key == QtCore.Qt.Key_Down:
            if len(self.history):
                self.pointer += 1
                if self.pointer == len(self.history):
                    self.pointer = 0
                self.__recall()
        else:
            e.ignore()
        self.ensureCursorVisible()

    def __recall(self):
        """
        Display the current item from the command history.
        """
        # Get a copy of the current cursor to make selection.
        cursor = self.textCursor()
        start_pos = cursor.position()
        cursor.setPosition(self.lineStartPos, QtGui.QTextCursor.KeepAnchor)
        after_pos = cursor.position()
        self.setTextCursor(cursor)

        # Remove the selected text.
        self.textCursor().removeSelectedText()

        # Keep around what we were trying to type.
        self.history.append(QtCore.QString(self.line))
        self.__clearLine()
        self.__insertText(self.history[self.pointer])
        
    def focusNextPrevChild(self, next):
        """
        Suppress tabbing to the next window in multi-line commands. 
        """
        if next and self.more:
            return 0
        return QtGui.QTextEdit.focusNextPrevChild(self, next)

    def mousePressEvent(self, e):
        """
        Keep the cursor after the last prompt.
        """
        if e.button() == QtCore.Qt.LeftButton:
            #self.moveCursor(QtGui.QTextEdit.MoveEnd, 0)
            cursor = self.textCursor()
            cursor.movePosition(QtGui.QTextCursor.End)
            self.setTextCursor(cursor)
        return

    def contentsContextMenuEvent(self,ev):
        """
        Suppress the right button context menu.
        """
        return
